import sys
import os
import json
# 引用sip以便打包时使用
from PyQt5 import sip
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QDialog, QAbstractItemView, QPushButton, QTableView
from PyQt5.QtCore import QTimer
from Ui_terminal import Ui_MainWindow
from login import Ui_Dialog

from py_ctp.enums import OrderStatus, OffsetType, DirectType, OrderType
from py_ctp.structs import InfoField, InstrumentField, Tick, TradingAccount, PositionField, OrderField, TradeField
from py_ctp.trade import CtpTrade
from py_ctp.quote import CtpQuote
from HaifengTableModel import HaifengTableModel
from color_log import Logger


class Terminal(QMainWindow):
    """"""

    on_connect_signal = pyqtSignal(object)
    on_disconnect_signal = pyqtSignal(object, int)
    on_login_signal = pyqtSignal(object, InfoField)  # 声名信号
    on_login_err_signal = pyqtSignal(InfoField)
    add_item_tableview = pyqtSignal(QTableView, object)

    def __init__(self):
        super().__init__()

        self.first = False
        # 读取配置
        self.servers = {}
        self.config = {}
        with open('./config.json', 'r', encoding='utf-8') as cfg:
            self.config = json.load(cfg)
            self.servers = self.config['ctp_config']
        self.t = CtpTrade()
        self.q = CtpQuote()

        self.dialog = QDialog()
        self.dlg_login = Ui_Dialog()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.refresh)

        self.t.OnConnected = lambda obj: self.on_connect_signal.emit(obj)
        self.t.OnUserLogin = lambda obj, info: self.on_login_signal.emit(obj, info)
        self.t.OnDisConnected = lambda obj, reason: self.on_disconnect_signal.emit(obj, reason)
        self.t.OnInstrumentStatus = lambda x, y, z: str(z)
        self.t.OnOrder = self.on_order
        self.t.OnCancel = self.on_cancel
        self.t.OnErrOrder = self.on_err_order
        self.t.OnErrCancel = self.on_err_order
        self.t.OnTrade = self.on_trade

        self.q.OnConnected = lambda obj: self.on_connect_signal.emit(obj)
        self.q.OnUserLogin = lambda obj, info: self.on_login_signal.emit(obj, info)
        self.q.OnDisConnected = lambda obj, reason: self.on_disconnect_signal.emit(obj, reason)
        self.q.OnTick = self.on_tick

        # 界面信息槽设置
        self.on_connect_signal.connect(self.on_front_connected)
        self.on_disconnect_signal.connect(self.on_front_disconnected)
        self.on_login_signal.connect(self.on_rsp_user_login)  # 连接信号和槽
        self.on_login_err_signal.connect(self.on_rsp_user_login_err)
        self.add_item_tableview.connect(self.add_item_to_tableview)

        # 变量初始化
        self.TradingDay = self.front_trade = self.front_quote = self.broker = self.investor = self.pwd = ''

        # 日志
        self.log = Logger()

        # 设置界面颜色
        self.ui = Ui_MainWindow()
        with open('./image/blue.css') as f:
            self.setStyleSheet(f.read())

        # 数据
        self.account_model = HaifengTableModel(TradingAccount)
        self.position_model = HaifengTableModel(PositionField)
        self.trade_model = HaifengTableModel(TradeField)
        self.order_all_model = HaifengTableModel(OrderField)
        self.order_notfilled_model = HaifengTableModel(OrderField)
        self.quote_model = HaifengTableModel(Tick)
        self.instrument_model = HaifengTableModel(InstrumentField)

        # 执行登录
        self.login()

    def on_rsp_user_login_err(self, info: InfoField):
        self.t.ReqUserLogout()
        QMessageBox(QMessageBox.Critical, '登录错误', str(info), QMessageBox.Ok).exec()

    def add_item_to_tableview(self, view: QTableView, obj: object):
        selected = [i.row() for i in view.selectedIndexes()]
        view.model().beginResetModel()
        view.model().objects.append(obj)
        view.model().endResetModel()
        if len(selected) > 0:
            for i in selected:
                view.selectRow(i)

    # 登录界面
    def login(self):
        """"""
        self.dlg_login.setupUi(self.dialog)
        self.dlg_login.comboBox_server.addItems(self.servers.keys())

        self.dlg_login.comboBox_server.setCurrentText(self.config['ctp_front'])
        self.dlg_login.lineEdit_investor.setText(self.config['investor'])
        self.dlg_login.lineEdit_pwd.setText(self.config['pwd'])
        self.dlg_login.lineEdit_appid.setText(self.config['appid'])
        self.dlg_login.lineEdit_authcode.setText(self.config['authcode'])

        self.dlg_login.pushButton_login.clicked.connect(lambda: self.login_api())
        self.dlg_login.pushButton_exit.clicked.connect(lambda: self.logout())
        self.dialog.show()
        self.dialog.exec_()
        if self.dialog.Rejected:
            return False
        self.show_terminal()

    # 登录
    def login_api(self):
        """"""
        self.front_trade = self.servers[self.dlg_login.comboBox_server.currentText()]['trade']
        self.front_quote = self.servers[self.dlg_login.comboBox_server.currentText()]['quote']
        self.broker = self.servers[self.dlg_login.comboBox_server.currentText()]['broker']
        self.log.war('connected to ' + self.front_trade)
        self.t.ReqConnect(self.front_trade)

    # 界面退出
    def logout(self):
        if self.dialog.isVisible():
            self.dialog.Rejected = True
            self.dialog.reject()
        if self.t.logined:
            self.t.ReqUserLogout()
        sys.exit(-1)

    # 连接
    def on_front_connected(self, obj):
        """前置连接成功"""
        if self.dlg_login is not None:
            self.dlg_login.progressBar.setValue(self.dlg_login.progressBar.value() + 20)
        if type(obj) is CtpTrade:
            self.investor = self.dlg_login.lineEdit_investor.text()
            self.pwd = self.dlg_login.lineEdit_pwd.text()
            self.log.war('t:connected')
            self.t.ReqUserLogin(self.investor, self.pwd, self.broker, '@hf', self.dlg_login.lineEdit_appid.text(), self.dlg_login.lineEdit_authcode.text())
        else:
            self.log.war('q:connected')
            self.q.ReqUserLogin(self.investor, self.pwd, self.broker)

    # 断开
    def on_front_disconnected(self, obj, reason: int):
        """前置断开"""
        if type(obj) is CtpTrade:
            self.log.war('t:disconnectd:{}'.format(reason))
        else:
            self.log.war('q:disconnectd:{}'.format(reason))

    # 登录返回
    def on_rsp_user_login(self, obj, info: InfoField):
        """登录结果响应"""
        if type(obj) is CtpTrade:
            self.log.info('t:{}'.format(info))

            if info.ErrorID == 0:
                self.TradingDay = self.t.tradingday
                self.q.ReqConnect(self.front_quote)
                self.dlg_login.progressBar.setValue(self.dlg_login.progressBar.value() + 20)
            else:
                self.dlg_login.progressBar.setValue(0)
                self.on_login_err_signal.emit(info)
        else:
            for v in self.t.positions.values():
                # v = PositionField()
                self.q.ReqSubscribeMarketData(v.InstrumentID)
            if self.dialog is not None:
                self.dialog.Rejected = False
                self.dlg_login.progressBar.setValue(100)
                self.dialog.accept()

    # 登录成功:显示主界面,绑定数据
    def show_terminal(self):
        """显示主界面"""
        self.ui.setupUi(self)

        self.ui.tableView_position.doubleClicked.connect(self.fast_close_posi)
        self.ui.tableView_notfilled.doubleClicked.connect(self.fast_cancel)
        self.ui.pushButton_pricetype.clicked.connect(self.pricetype_change)
        self.ui.comboBox_instrument.addItems(sorted(self.t.instruments.keys()))
        self.ui.comboBox_instrument.currentTextChanged.connect(self.inst_changed)
        self.ui.pushButton_bk.clicked.connect(self.order)
        self.ui.pushButton_sk.clicked.connect(self.order)
        self.ui.pushButton_bp.clicked.connect(self.order)
        self.ui.pushButton_sp.clicked.connect(self.order)
        self.show()
        # 启动界面刷新
        self.timer.start(500)

    def fast_close_posi(self):
        row = [i.row() for i in self.ui.tableView_position.selectedIndexes()][0]
        posi: PositionField = self.position_model.objects[row]
        dire = DirectType.Sell if posi.Direction == DirectType.Buy else DirectType.Buy
        tick: Tick = self.q.inst_tick[posi.InstrumentID]
        price = tick.UpperLimitPrice if dire == DirectType.Buy else tick.LowerLimitPrice
        if posi.TdPosition > 0:
            self.t.ReqOrderInsert(posi.InstrumentID, dire, OffsetType.CloseToday, price, posi.TdPosition)
        if posi.YdPosition > 0:
            self.t.ReqOrderInsert(posi.InstrumentID, dire, OffsetType.Close, price, posi.YdPosition)

    def fast_cancel(self):
        row = [i.row() for i in self.ui.tableView_notfilled.selectedIndexes()][0]
        order: OrderField = self.order_notfilled_model.objects[row]
        self.t.ReqOrderAction(order.OrderID)

    def pricetype_change(self):
        sender: QPushButton = self.sender()
        sender.setText('跟' if sender.text() != '跟' else '限')

    def inst_changed(self, inst: str):
        self.q.ReqSubscribeMarketData(inst)
        inst_info: InstrumentField = self.t.instruments[inst]
        self.ui.doubleSpinBox_price.setSingleStep(inst_info.PriceTick)
        self.ui.doubleSpinBox_price.setDecimals(0 if inst_info.PriceTick >= 1 else len(str(0.05).split('.')[1]))

    def order(self):
        sender: QPushButton = self.sender()
        do = sender.objectName()[-2:]
        dire = DirectType.Buy if do[0] == 'b' else DirectType.Sell
        price = self.ui.doubleSpinBox_price.value()
        lots = self.ui.spinBox_lots.value()
        inst = self.ui.comboBox_instrument.currentText()

        if do[1] == 'k':
            self.t.ReqOrderInsert(inst, dire, OffsetType.Open, price, lots, OrderType.Limit)
        else:
            key = '{0}_{1}'.format(inst, 'Sell' if dire == DirectType.Buy else 'Buy')
            pf: PositionField = self.t.positions.get(key)
            if pf:
                if pf.TdPosition > 0:
                    td_lots = min(pf.TdPosition, lots)
                    self.t.ReqOrderInsert(inst, dire, OffsetType.CloseToday, price, td_lots)
                    lots -= td_lots
                if lots > 0:
                    self.t.ReqOrderInsert(inst, dire, OffsetType.Close, price, lots)

    def refresh(self):
        for view in [self.ui.tableView_account, self.ui.tableView_all, self.ui.tableView_notfilled, self.ui.tableView_position, self.ui.tableView_quote, self.ui.tableView_trade]:
            view.repaint()
            view.update()

        if not self.first:  # show前绑定,权益会不显示
            self.first = True
            tv = [self.ui.tableView_instrument, self.ui.tableView_position, self.ui.tableView_account, self.ui.tableView_all, self.ui.tableView_notfilled, self.ui.tableView_quote, self.ui.tableView_trade]
            model = [self.instrument_model, self.position_model, self.account_model, self.order_all_model, self.order_notfilled_model, self.quote_model, self.trade_model]
            objs = [list(self.t.instruments.values()), [p for p in self.t.positions.values() if p.Position > 0], [self.t.account], list(self.t.orders.values()),
                    [o for o in self.t.orders.values() if o.Status == OrderStatus.Normal or o.Status == OrderStatus.Partial],
                    list(self.q.inst_tick.values()), list(self.t.trades.values())]
            # 禁止编辑 & 数据绑定
            for i in range(0, len(tv)):
                tv[i].setEditTriggers(QAbstractItemView.NoEditTriggers)
                tv[i].setModel(model[i])
                model[i].beginResetModel()
                model[i].objects = objs[i]
                model[i].endResetModel()
                tv[i].resizeColumnsToContents()

        # 刷新持仓
        show_posis = [p for p in self.t.positions.values() if p.Position > 0]
        for p in [posi for posi in show_posis if posi not in self.position_model.objects]:
            self.add_item_to_tableview(self.ui.tableView_position, p)
        for p in [posi for posi in self.position_model.objects if posi not in show_posis]:
            self.position_model.objects.remove(p)

        # 行情显示
        for tick in self.q.inst_tick.values():
            if tick.Instrument not in [o.Instrument for o in self.quote_model.objects]:
                self.add_item_to_tableview(self.ui.tableView_quote, tick)

        # 委托价格显示
        tick: Tick = self.q.inst_tick.get(self.ui.comboBox_instrument.currentText())
        if tick is not None:
            if self.ui.pushButton_pricetype.text() == '跟':
                self.ui.doubleSpinBox_price.setValue(tick.LastPrice)
            decimals = self.ui.doubleSpinBox_price.decimals()
            self.ui.label_askprice.setText(format(tick.AskPrice, '.{}f'.format(decimals)))
            self.ui.label_bidprice.setText(format(tick.BidPrice, '.{}f'.format(decimals)))

    def on_order(self, obj, order: OrderField):
        """委托响应"""
        if order not in self.order_all_model.objects:
            self.order_all_model.beginResetModel()
            self.order_all_model.objects.append(order)
            self.order_all_model.endResetModel()

            self.order_notfilled_model.beginResetModel()
            self.order_notfilled_model.objects.append(order)
            self.order_notfilled_model.endResetModel()
        elif order in self.order_notfilled_model.objects:
            if order.Status == OrderStatus.Filled:
                self.order_notfilled_model.beginResetModel()
                self.order_notfilled_model.objects.remove(order)
                self.order_notfilled_model.endResetModel()

    def on_cancel(self, obj, order: OrderField):
        """撤单响应"""
        if order in self.order_notfilled_model.objects:
            self.order_notfilled_model.beginResetModel()
            self.order_notfilled_model.objects.remove(order)
            self.order_notfilled_model.endResetModel()

    def on_err_order(self, obj, order: OrderField, info: InfoField):
        """错单响应"""
        if order in self.order_notfilled_model.objects:
            self.order_notfilled_model.objects.remove(order)
        if order not in self.order_all_model.objects:
            self.order_all_model.beginResetModel()
            self.order_all_model.objects.append(order)
            self.order_all_model.endResetModel()

    def on_err_cancel(self, obj, order: OrderField, info: InfoField):
        pass

    def on_trade(self, obj, trade: TradeField):
        """成交响应"""
        if trade not in self.trade_model.objects:
            self.trade_model.beginResetModel()
            self.trade_model.objects.append(trade)
            self.trade_model.endResetModel()

    def on_tick(self, obj, tick: Tick):
        pass
        # if tick.Instrument not in [o.Instrument for o in self.quote_model.objects]:
        #     self.add_item_to_tableview(self.ui.tableView_quote, tick)


if __name__ == '__main__':
    app = QApplication(sys.argv)

    t = Terminal()

    app.exec_()
